import { RUNNER_PUPPETRY } from "constant";

import { result } from "service/utils";
import { renderSuiteHtml } from "./interactive-mode";
import { TestGeneratorError } from "error";
import fs from "fs";
import { join } from "path";

const NETWORK_TIMEOUT = 50000,
      INTERACTIVE_MODE_TIMEOUT = 1800000, // 30min

      normalizeName = ( str ) => {
        const re = /[^a-zA-Z0-9_-]/g;
        return str.replace( re, "--" );
      },

      readInteractAsset = ( outputDirectory, file ) => fs.readFileSync(
        join( outputDirectory, "lib", "interactive-mode", file ), "utf8"
      );

export function buildShadowDomQuery( targetChain ) {
  const code = targetChain.reduce( ( carry, target, inx ) => {
    if ( !target.css ) {
      throw new TestGeneratorError( `Shadow DOM queries currently support only CSS selectors` );
    }
    if ( inx === 0 ) {
      return `document.querySelector( "${ target.selector }" )`;
    }
    return `${ carry }.shadowRoot.querySelector( "${ target.selector }" )`;
  }, "" );
  return `await bs.page.evaluateHandle('${ code }')`;
}

export const tplQuery = ( targetChain ) => {
  const target = targetChain[ targetChain.length - 1 ],
        str = JSON.stringify.bind( JSON );

  let fnBody = ( target.parentType === "shadowHost"
    ? `bs.tryLocalTarget( \`${ target.target }\`, async () => ${ buildShadowDomQuery( targetChain ) } )`
    : ( targetChain.length === 1
      ? `await bs.query( ${ str( target.selector ) }, ${ str( target.css ) }, ${ str( target.target ) } )`
      : `await bs.queryChain( ${ str( targetChain )}, ${ str( target ) } )` )
  );

  return `bs.TARGETS[ "${ target.target }" ] = async () => ${ fnBody };`;

};

function buildEnv( env ) {
  if ( !env || !env.variables ) {
    return "";
  }
  const body = Object.entries( env.variables )
    .map( ([ k, v ]) => `  "${ k }": "${ v }"` )
    .join( ",\n" );
  return `// Environment variables
let ENV = {
${ body }
};`;
}

function getSetupOptions( options ) {
  return JSON.stringify( options );
}

/**
 * \" breaks JavaScript for injecting
 * @param {String} data
 * @returns {String}
 */
function stringifyData( data ) {
  /*eslint no-useless-escape: 0*/
  return ( JSON.stringify( data, null, 2 ) ).replace( /\\\"/gm, "'" );
}
export const tplSuite = ({
  title, body, targets, suite, runner, projectDirectory, outputDirectory, env, options, interactive, snippets
}) => `
/**
 * Generated by https://github.com/dsheiko/puppetry
 * on ${ String( Date() ) }
 * Suite: ${ suite.title }
 */

${ runner !== RUNNER_PUPPETRY ? `var nVer = process.version.match( /^v(\\d+)/ );
if ( !nVer || nVer[ 1 ] < 9 ) {
  console.error( "WARNING: You have an outdated Node.js version " + process.version
    + ". You need at least v.9.x to run this test suite." );
}
` : `` }

const {
        bs, util, fetch, localStorage
      } = require( "../lib/bootstrap" )( ${ JSON.stringify( normalizeName( title ) ) } ),
      puppeteerOptions = require( "../puppeteer.config.json" ),
      devices = require( "puppeteer" ).devices;


${ runner === RUNNER_PUPPETRY ? `
util.setProjectDirectory( ${ JSON.stringify( projectDirectory ) } );
` : `` }

jest.setTimeout( ${  result( options, "debug", 0 )
    ? INTERACTIVE_MODE_TIMEOUT
    : ( options.interactiveMode
      ? INTERACTIVE_MODE_TIMEOUT
      : ( suite.timeout || NETWORK_TIMEOUT ) )
} );

let consoleLog = [], // assetConsoleMessage
    dialogLog = []; // assertDialog;

bs.TARGETS = {};

${ buildEnv( env ) }

${ targets }

describe( ${ JSON.stringify( title ) }, () => {
  beforeAll(async () => {
    await bs.setup( puppeteerOptions, ${ getSetupOptions( options ) });
    await util.once(async () => {
      bs.browser && console.log( "BROWSER: ", await bs.browser.version() );
      await util.savePuppetterInfo( bs );
    });

    bs.page.on( "console", ( message ) => consoleLog.push( message ) );
    bs.page.on( "dialog", ( dialog ) => dialogLog.push( dialog ) );

    ${ options.requireNetworkTraffic ? `bs.network.watchTraffic();` : `` }
    ${ options.requireInterceptTraffic ? `bs.performance.watchTraffic();` : `` }

    ${ options.interactiveMode ? `
    let stepIndex = 0;
    await bs.page.exposeFunction('setPuppetryStepIndex', index => {
      stepIndex = index;
    });

${ JSON.stringify( renderSuiteHtml( suite, snippets ) ) }

    bs.page.on( "load", async () => {
      await bs.page.addStyleTag({ content: \`${ readInteractAsset( outputDirectory, "toolbox.css" ) }\`});
      await bs.page.addScriptTag({ content: \`
        const data = ${ stringifyData( interactive )  };
        let stepIndex = \${ ( stepIndex ? parseInt( stepIndex, 10 ) : 0 )  };
        const suiteHtml = ${ JSON.stringify( renderSuiteHtml( suite, snippets ) ) };
        ${ readInteractAsset( outputDirectory, "toolbox.js" ) }
        \`});
    });
    ` : `` }
  });

  afterAll(async () => {
${ options.requireNetworkTraffic ? `    // Let it detach CDP session
    await bs.page.waitFor( 500 );`: `` }
    await bs.teardown();
  });

${body}

});
`;

export const tplGroup = ({ title, body }) => `
  describe( ${ JSON.stringify( title ) }, () => {
${body}
  });
`;

export const tplTest = ({ title, body }) => `
    test( ${ JSON.stringify( title ) }, async () => {
      let result, assert, searchStr, localEnv;
${body}
    });
`;